<template>
  <div class="container">
    <!-- 变色开关  -->
    <div id="changeColor">
      <img
        src="../../public/assets/变色.png"
        alt=""
        width="24"
        style="vertical-align: middle"
      />
      <span id="changeColorText">开始变色</span>
    </div>

    <!-- 颜色器  -->
    <div id="color">
      <div class="colorChoose" id="color1">
        <img src="../../public/assets/绿.jpg" />
      </div>
      <div class="colorChoose" id="color2">
        <img src="../../public/assets/灰.jpg" />
      </div>
      <div class="colorChoose" id="color3">
        <img src="../../public/assets/红.jpg" />
      </div>
      <div class="colorChoose" id="color4">
        <img src="../../public/assets/黑.jpg" />
      </div>
      <div class="colorChoose" id="color5">
        <img src="../../public/assets/白.jpg" />
      </div>
    </div>
    <div id="rotateAudio">
      <!-- 车灯控制  -->
      <div>
        <img
          id="light"
          src="../../public/assets/开车灯.png"
          alt=""
          width="24"
          style="vertical-align: middle"
        />
      </div>
      <!-- 旋转  -->
      <div id="rotate">
        <img
          id="rotateimg"
          src="../../public/assets/停止旋转.png"
          alt=""
          width="24"
          style="vertical-align: middle"
        />
      </div>
    </div>
  </div>
</template>

<script setup>
import { onMounted, ref } from "vue";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
// 引入镜头光晕扩展库
import {
  Lensflare,
  LensflareElement,
} from "three/examples/jsm/objects/Lensflare.js";
// 导入tween
import * as TWEEN from "three/examples/jsm/libs/tween.module.js";
import g from "three/examples/jsm/libs/lil-gui.module.min.js";

let colorAuto = ref(false); // 自动变色开关
const light = ref(false); // 车灯
let rotate = ref(false); // 旋转开关

// // * 实例化GUI对象
// const gui = new g();

// // * 改变GUI的样式
// gui.domElement.style.right = "10px";
// gui.domElement.style.top = "10px";

/**
 * 初始化设置
 */
const model = new THREE.Group();

// 初始化场景
const scene = new THREE.Scene();

// 初始化相机
const camera = new THREE.PerspectiveCamera(
  30,
  window.innerWidth / window.innerHeight,
  1,
  30000
);
camera.position.set(-621, 357, 357);
camera.lookAt(0, 0, 0);

// 初始化渲染器
const renderer = new THREE.WebGLRenderer({
  antialias: true, // 抗锯齿
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0xcccccc, 1);
renderer.setPixelRatio(window.devicePixelRatio); // 设置设备像素比率，防止 Canvas 画布输出模糊
renderer.outputEncoding = THREE.sRGBEncoding;
document.body.appendChild(renderer.domElement);

// 初始化控制器
const controls = new OrbitControls(camera, renderer.domElement);

// 相机距离观察目标点极小距离——模型最大状态
controls.minDistance = 500;
// 相机距离观察目标点极大距离——模型最小状态
controls.maxDistance = 1200;
// 上下旋转最大值设置
controls.maxPolarAngle = (Math.PI / 2) * 0.9;

const render = () => {
  TWEEN.update();
  requestAnimationFrame(render);
  renderer.render(scene, camera);
  controls && controls.update();
};

render();

// onresize 事件会在窗口被调整大小时发生
window.onresize = () => {
  renderer.setSize(window.innerWidth, window.innerHeight);
  // 全屏情况下：设置观察范围长宽比aspect为窗口宽高比
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
};

/**
 * 光源设置
 */
// 平行光1
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(400, 200, 300);
scene.add(directionalLight);

// 平行光2
const directionalLight2 = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight2.position.set(-400, -200, -300);
scene.add(directionalLight2);

// 环境光
const ambient = new THREE.AmbientLight(0xffffff, 2);
scene.add(ambient);

/**
 * 变色设置
 */
const colorArr16 = [0x023911, 0x222222, 0x6a030a, 0x000000, 0xffffff];
const colorArr = [];

// 转为十进制
for (let i = 0; i < colorArr16.length; i++) {
  const color = new THREE.Color(colorArr16[i]);
  colorArr.push({
    r: color.r,
    g: color.g,
    b: color.b,
  });
}

// mesh 颜色值(RBG):初始值
const meshColor = {
  r: colorArr[0].r,
  g: colorArr[0].g,
  b: colorArr[0].b,
};

/**
 * 变色动画原理：
 * 变色动画拆分为两部分：每种颜色都对应两个 tween 动画
 * 阶段一：一个是颜色不变状态
 * 阶段二：一个是颜色变为下一个状态
 * 通过 tween 的方法 chain()，把所有颜色对应的动画，串联起来，就可以变成一个整体动画。
 * 5 种颜色，每种 2 个动画阶段，共 10 种动画阶段（补间动画）
 */
// 方法：设置模型颜色
const setMeshColor = (r, g, b) => {
  model.traverse((node) => {
    if (node.type === "Mesh") {
      if (node.name.slice(0, 2) == "外壳") {
        node.material.color.setRGB(r, g, b);
      }
    }
  });
};

const tweenArr = [];

// 1. 批量创建动画片段tween
for (let i = 0; i < colorArr.length; i++) {
  // 阶段一：保持当前颜色
  const tween1 = new TWEEN.Tween(meshColor)
    .to(
      {
        r: colorArr[i].r,
        g: colorArr[i].g,
        b: colorArr[i].b,
      },
      1000
    )
    .onUpdate(() => {
      setMeshColor(meshColor.r, meshColor.g, meshColor.b);
    });
  // 阶段二：进入下一颜色
  const tween2 = new TWEEN.Tween(meshColor);
  if (i < colorArr.length - 1) {
    tween2
      .to(
        {
          r: colorArr[i + 1].r,
          g: colorArr[i + 1].g,
          b: colorArr[i + 1].b,
        },
        1000
      )
      .onUpdate(() => {
        setMeshColor(meshColor.r, meshColor.g, meshColor.b);
      });
  } else {
    tween2
      .to(
        {
          r: colorArr[0].r,
          g: colorArr[0].g,
          b: colorArr[0].b,
        },
        1000
      )
      .onUpdate(() => {
        setMeshColor(meshColor.r, meshColor.g, meshColor.b);
      });
  }
  tweenArr.push(tween1, tween2);
}

// chain 连接补间动画，批量连接所有动画片段:上一个补间结束的时候立即启动另外一个补间
for (let i = 0; i < tweenArr.length - 1; i++) {
  tweenArr[i].chain(tweenArr[i + 1]);
}
// 重要：首尾相接循环执行！！
tweenArr[tweenArr.length - 1].chain(tweenArr[0]);

// 批量生成动画，然后拼接
const colorTween = tweenArr[0];

/**
 * 灯光设置
 */
// 加载灯光纹理贴图
const texLoader = new THREE.TextureLoader();
// 用于镜头光晕的纹理对象
const textureLensflare = texLoader.load("./models/lensflare.jpg");
// 创建两个镜头光晕 Lensflare 对象，分别用来模拟车前面左右两个车灯的发光效果
const lensflareElement = new LensflareElement(
  textureLensflare,
  600, // 设置光晕像素大小
  0,
  new THREE.Color(0xffffff)
);

// 创建灯光对象
// 灯光一
const lensflare1 = new Lensflare();
lensflare1.addElement(lensflareElement); // Lensflare可以根据需要包含多个LensflareElement
// 灯光二
const lensflare2 = new Lensflare();
lensflare2.addElement(lensflareElement);

// 初始化：镜头光晕对象隐藏
lensflare1.visible = false;
lensflare2.visible = false;

/**
 * 精灵设置
 */
const createCarTags = (carModel) => {
  const scale = 15; //精灵缩放倍数
  // 模型标签名
  const tagNameArr = [
    "右前光标",
    "右后光标",
    "左前光标",
    "左后光标",
    "后备箱光标",
  ];
  // 标签数组(精灵图)
  const tagArr = [];

  tagNameArr.forEach((name) => {
    // 配置贴图
    const spriteMaterial = new THREE.SpriteMaterial({
      map: new THREE.TextureLoader().load("./models/光点.png"), // 设置精灵纹理贴图
      transparent: true,
    });
    // 创建精灵
    const sprite = new THREE.Sprite(spriteMaterial);
    sprite.scale.set(scale, scale, 1); // 大小设置

    // 指定模型部分插入精灵图
    const tagObj = carModel.getObjectByName(name);
    tagObj.add(sprite);

    // 调整位置：根据 sprite 大小平移，任意观察角度都避免精灵插入到车体内
    if (name == "右前光标" || name == "右后光标") {
      sprite.position.z -= sprite.scale.x / 2;
    } else if (name == "左前光标" || name == "左后光标") {
      sprite.position.z += sprite.scale.x / 2;
    } else if (name == "后备箱光标") {
      sprite.position.x += sprite.scale.x / 2;
    }

    tagArr.push(sprite);
  });

  // 设置标注精灵Sprite波动，提示用户点击
  let s = 0.0;

  // 设置帧动画
  const waveAnimation = () => {
    // 每次自增0.01
    s += 0.01;
    tagArr.forEach((sprite) => {
      // 放大动画
      if (s < 0.5) {
        sprite.scale.x = scale * (1 + s);
        sprite.scale.y = scale * (1 + s);
      } else if (s >= 0.5 && s < 1.0) {
        // 缩放动画
        sprite.scale.x = scale * (2 - s);
        sprite.scale.y = scale * (2 - s);
      } else {
        // 极限值：超出或等于1，则重置为0
        s = 0.0;
      }
    });

    requestAnimationFrame(waveAnimation);
  };
  waveAnimation();
};

/**
 * 车门设置
 */
const chooseArr = [];

const open = (carModel) => {
  const tagNameArr = [
    "右前光标",
    "右后光标",
    "左前光标",
    "左后光标",
    "后备箱光标",
  ];
  const doorNameArr = ["右前门", "右后门", "左前门", "左后门", "后备箱"];

  tagNameArr.forEach((name, i) => {
    var tagObj = carModel.getObjectByName(name).children[0];
    chooseArr.push(tagObj);
    // 自定义光标 door 属性，绑定光标对应的车门
    tagObj.door = carModel.getObjectByName(doorNameArr[i]);
    tagObj.door.state = "close"; // 车门状态

    // 选中车门的名字
    const door = tagObj.door;
    const doorName = tagObj.door.name;
    if (doorName == "右前门" || doorName == "右后门") {
      door.openTween = openClose("y", 0, Math.PI / 3, door, "open");
      door.closeTween = openClose("y", Math.PI / 3, 0, door, "close");
    } else if (doorName == "左前门" || doorName == "左后门") {
      door.openTween = openClose("y", 0, -Math.PI / 3, door, "open");
      door.closeTween = openClose("y", -Math.PI / 3, 0, door, "close");
    } else if (doorName == "后备箱") {
      door.openTween = openClose("z", 0, Math.PI / 3, door, "open");
      door.closeTween = openClose("z", Math.PI / 3, 0, door, "close");
    }
  });
};

// 射线投射器 Raycaster 的射线拾取选中网格模型对象函数 choose()，选中的网格模型变为半透明效果
const choose = (event) => {
  const Sx = event.clientX; // 鼠标单击位置横坐标
  const Sy = event.clientY; // 鼠标单击位置纵坐标
  // 屏幕坐标转 WebGL 标准设备坐标
  const x = (Sx / window.innerWidth) * 2 - 1; // WebGL标准设备横坐标
  const y = -(Sy / window.innerHeight) * 2 + 1; // WebGL标准设备纵坐标
  // 创建一个射线投射器 Raycaster
  const raycaster = new THREE.Raycaster();
  // 通过鼠标单击位置标准设备坐标和相机参数计算射线投射器 Raycaster 的射线属性 .ray
  raycaster.setFromCamera(new THREE.Vector2(x, y), camera);
  // 返回 .intersectObjects() 参数中射线选中的网格模型对象
  // 未选中对象返回空数组[],选中一个数组1个元素，选中两个数组两个元素
  const intersects = raycaster.intersectObjects(chooseArr);
  // console.log("射线器返回的对象", intersects);
  // intersects.length 大于 0 说明，说明选中了模型
  if (intersects.length > 0) {
    // 选中的车门
    const chooseDoor = intersects[0].object.door;
    if (chooseDoor.state == "close") {
      chooseDoor.state = "open";
      chooseDoor.openTween.start(); //播放开门动画
    } else {
      chooseDoor.state = "close";
      chooseDoor.closeTween.start(); //播放关门动画
    }
  }
};
addEventListener("click", choose); // 监听窗口鼠标单击事件

// 创建模型对象旋转动画
const openClose = (axis, angle1, angle2, door, openClose) => {
  const state = {
    angle: angle1, // 车门动画开始角度
  };
  const tween = new TWEEN.Tween(state);
  tween.to(
    {
      angle: angle2, // 车门动画结束角度
    },
    1000
  );
  tween.onUpdate(() => {
    if (axis == "y") {
      door.rotation.y = state.angle;
    } else {
      door.rotation.z = state.angle;
    }
  });
  return tween;
};

/**
 * 模型设置
 */
// 加载环境贴图
const textureCube = new THREE.CubeTextureLoader()
  .setPath("./models/envMap/")
  .load(["px.jpg", "nx.jpg", "py.jpg", "ny.jpg", "pz.jpg", "nz.jpg"]);
textureCube.colorSpace = THREE.SRGBColorSpace; // 设置为 SRGB 颜色空间

// 加载模型
const gltfLoader = new GLTFLoader();

gltfLoader.load("./models/轿车.glb", (gltf) => {
  model.add(gltf.scene);
  scene.add(model);

  // 灯光载入模型
  const light1 = model.getObjectByName("镜头光晕1");
  const light2 = model.getObjectByName("镜头光晕2");

  light1.add(lensflare1);
  light2.add(lensflare2);

  // 标注热点
  createCarTags(gltf.scene);

  // 车门开关
  open(gltf.scene);

  model.traverse((node) => {
    if (node.type !== "Mesh") return;
    switch (node.name.slice(0, 2)) {
      case "外壳": //整个外壳、相同材质的车门外壳等
        node.material = new THREE.MeshPhysicalMaterial({
          color: node.material.color, //默认颜色
          clearcoat: 1,
          clearcoatRoughness: 0.01,
          metalness: 0.9,
          roughness: 0.5,
          envMapIntensity: 2.5,
        });
        break;
      case "玻璃":
        node.material = new THREE.MeshPhysicalMaterial({
          color: 0xffffff,
          metalness: 0,
          roughness: 0,
          transparent: true,
          transmission: 1.0, //transmission替代opacity  envMapIntensity: 2.5,
        });
        break;
      case "轮胎":
        node.material.color.set(0x000000);
        node.material.normalScale.set(2, 2); //加强法线贴图凹凸效果
        node.material.metalness = 0.0;
        node.material.roughness = 0.6;
        break;
      case "塑料":
        node.material = new THREE.MeshPhysicalMaterial({
          color: 0x010101,
          metalness: 0.0,
          roughness: 0.8,
          envMapIntensity: 1.0,
        });
        break;
      default:
        break;
    }
    switch (node.name.slice(0, 3)) {
      case "后视镜":
        node.material = new THREE.MeshStandardMaterial({
          color: 0xffffff, //白色
          metalness: 1.0,
          roughness: 0.0,
          //   envMapIntensity控制环境贴图的影响
          envMapIntensity: 1.0,
        });
        break;
      case "前灯罩":
        node.material = new THREE.MeshPhysicalMaterial({
          color: 0xffffff,
          metalness: 0,
          roughness: 0,
          // envMapIntensity: 1.0,
          transmission: 0.9,
          transparent: true,
          envMapIntensity: 2.5,
        });
        break;

      default:
        break;
    }
    switch (node.name.slice(0, 4)) {
      case "高光金属":
        node.material = new THREE.MeshStandardMaterial({
          color: node.material.color, //读取材质原来的颜色
          metalness: 1.0,
          roughness: 0.2,
          //   envMapIntensity控制环境贴图的影响
          envMapIntensity: 1.0,
        });
        break;
      case "尾灯灯罩":
        node.material = new THREE.MeshPhysicalMaterial({
          color: 0xff0000,
          metalness: 0,
          roughness: 0,
          transmission: 0.5,
          transparent: true,
          envMapIntensity: 2.5,
        });
        break;
      case "尾灯发光":
        node.material = new THREE.MeshLambertMaterial({
          color: 0x660000,
        });
        break;
      default:
        break;
    }
    switch (node.name.slice(0, 5)) {
      case "尾灯第二层":
        node.material = new THREE.MeshPhysicalMaterial({
          color: 0x440000,
          metalness: 0,
          roughness: 0,
          transmission: 0.5,
          transparent: true,
        });
        break;
      case "尾灯第三层":
        node.material = new THREE.MeshLambertMaterial({
          color: 0x19190000,
        });
        break;
      default:
        break;
    }
    // 天窗黑玻璃
    model.getObjectByName("天窗黑玻璃").material =
      new THREE.MeshPhysicalMaterial({
        color: 0xffffff,
        metalness: 0,
        roughness: 0,
        envMapIntensity: 1.0,
        transmission: 1.0, // .transmission属性用于设置玻璃材质
        transparent: true,
      });
    // 车座
    model.getObjectByName("车座").material = new THREE.MeshPhysicalMaterial({
      color: 0x020202,
      metalness: 0.0,
      roughness: 0.6,
      envMapIntensity: 1.0,
    });

    //批量设置环境贴图
    node.material.envMap = textureCube;
  });
});

/**
 * 地面设置
 */
const geometry = new THREE.PlaneGeometry(6000, 6000);
const texture = new THREE.TextureLoader().load("./models/瓷砖.jpg");
texture.colorSpace = THREE.SRGBColorSpace; // 设置为 SRGB 颜色空间
// 设置阵列（贴图类型）
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(12, 12); // uv两个方向纹理重复数量

// 设置贴图
const material = new THREE.MeshLambertMaterial({
  color: 0x222222,
  map: texture,
});

const ground = new THREE.Mesh(geometry, material);
ground.rotateX(-Math.PI / 2);

scene.add(ground);

/**
 * 操作
 */
// 自动变色
const colorAutoEvent = () => {
  document.getElementById("changeColor").onclick = () => {
    if (colorAuto.value) {
      colorTween.stop(); // 停止动画
      colorAuto.value = false;
      document.getElementById("changeColorText").innerHTML = "开始变色";
      // setMeshColor(0x023911); // 动画停止，颜色回到最初的状态
    } else {
      colorTween.start(); // 开始动画
      colorAuto.value = true;
      document.getElementById("changeColorText").innerHTML = "停止变色";
    }
  };
};

// 按钮切换颜色
const setColor = () => {
  const colorArr = [0x023911, 0x222222, 0x6a030a, 0x000000, 0xffffff];
  colorArr.forEach((color, i) => {
    const dom = document.getElementById("color" + (i + 1));
    dom.onclick = () => {
      model.traverse((node) => {
        if (node.type !== "Mesh") return;
        if (node.name.slice(0, 2) == "外壳") {
          node.material.color.set(color);
        }
      });
    };
  });
};

// 操作车灯
const lightEvent = () => {
  document.getElementById("light").onclick = () => {
    if (light.value) {
      // 镜头光晕对象隐藏
      lensflare1.visible = false;
      lensflare2.visible = false;
      // 亮光
      directionalLight.intensity = 0.8;
      directionalLight2.intensity = 0.8;
      ambient.intensity = 2;
      light.value = false;
      document.getElementById("light").src = "./assets/开车灯.png";
    } else {
      // 镜头光晕对象可见
      lensflare1.visible = true;
      lensflare2.visible = true;
      // 弱光：弱光下，车灯发光对比更明显
      directionalLight.intensity = 0.0;
      directionalLight2.intensity = 0.0;
      ambient.intensity = 0.3;
      light.value = true;
      document.getElementById("light").src = "./assets/关车灯.png";
    }
  };
};

// 模型旋转
const rotateEvent = () => {
  let rotateAnimation = null;
  const loop = () => {
    rotateAnimation = requestAnimationFrame(loop);
    scene.rotateY(0.002);
  };
  document.getElementById("rotate").onclick = () => {
    if (rotate.value) {
      cancelAnimationFrame(rotateAnimation);
      rotate.value = false;
      document.getElementById("rotateimg").src = "./assets/停止旋转.png";
    } else {
      loop();
      rotate.value = true;
      document.getElementById("rotateimg").src = "./assets/旋转.png";
    }
  };
};

onMounted(() => {
  colorAutoEvent();
  setColor();
  lightEvent();
  rotateEvent();
});
</script>

<style scoped>
#color {
  width: 410px;
  position: fixed;
  background: rgba(0, 0, 0, 0.4);
  padding: 10px 16px;
  border-radius: 6px;
  left: 50%;
  margin-left: -190px;
  top: 100%;
  margin-top: -80px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.colorChoose img {
  width: 50px;
  border-radius: 25px;
}

#changeColor {
  color: #ffffff;
  font-size: 16px;
  padding: 8px 16px;
  background: rgba(0, 0, 0, 0.4);
  border-radius: 30px;
  height: 42px;
  line-height: 28px;

  cursor: pointer;
  position: fixed;
  right: 10px;
  top: 50%;
  margin-top: -22px;
}

#rotateAudio {
  position: fixed;
  left: 20px;
  top: 50%;
  margin-top: -55px;
}

#rotateAudio div {
  padding: 10px 10px;
  background: rgba(0, 0, 0, 0.4);
  border-radius: 22px;
  cursor: pointer;
}
</style>
